#!/usr/bin/python3
# -*- coding:utf-8 -*-
""""
   @Author: 即将拥有人鱼线的yjw
   @Time: 2024/3/8 14:50
   @File: bladeCaseOperate.py
   @desc: 用例上传脚本
"""
import json
import os
import re
import uuid
from urllib.parse import quote

import jsonpath
from BladeFerm.Lib.Common import logger
from BladeFerm.utils.OperationExcel import OperationExcel
from BladeFerm.ApiModule.httpRequest import HttpRequest
from BladeFerm.utils.dataoperation import YamlOperation


class CaseStatus:
    def __init__(self):
        # 记录上传用例的结果
        self.upload_case_result = dict()

    def set_progress(self, username: str, progress: float):
        self.upload_case_result[username] = progress

    def get_progress(self, username: str):
        progress = {'step': -1, 'status': f'没有此用户：{username}', 'count': 0, 'total': 0}
        return self.upload_case_result.get(username, progress)


class ExcelCaseDict:
    def __init__(self):
        self.caseNode: dict = dict()        # 记录blade上用例的基础信息
        self.stepNode: dict = dict()        # 记录用例步骤的信息  用例id，数据id，脚本id，前置条件id，数据校验id等等
        self.stepOrder = 1
        self.stepsHeader = dict()           # 记录blade用例中表格头信息  主要是前置条件和备注信息的键名称是可变的
        self.commonParams: dict = dict()
        self.isNewCase: bool = True         # 是否是新的一条用例
        self.lastCaseNo: str = ''           # 记录上一条用例的流水号  // 改成记录测试点，新增用例可能无用例编号
        self.priorityList = ['P0', 'P1', 'P2', 'P3']
        # excel中的用例
        self.caseNo: str = ''             # 流水号
        self.caseName: str = ''           # 测试点，用例名称
        self.priority: str = ''           # 优先级
        self.isPass: str = ''            # 正反例
        self.preconditions: str = ''      # 前置条件
        self.operator: str = ''           # 操作员
        self.menuFunction: str = ''       # 菜单功能
        self.caseStep: str = ''           # 操作步骤
        self.inputData: str = ''          # 测试数据
        self.inputDataDict: dict = dict()       # 处理后的测试数据
        self.expectResult: str = ''             # 期望结果
        self.expectResultDict: dict = dict()    # 处理后的期望结果
        self.expectResultString: str = ''       # blade测试数据期望格式
        self.dataDesignDesc: str = ''     # 数据设计说明
        self.uiExpectResult: str = ''     # 界面期望检出
        self.relatedDemand: str = ''      # 关联需求单
        self.relatedTask: str = ''        # 关联任务单
        self.casePath: str = ''           # 用例路径
        self.scriptName: str = ''          # 脚本名称
        # blade中的用例
        self.paramsDF = None               # 用例参数

    def __updateCommonParams(self):
        params = {'caseNo': self.caseNo,
                  'caseName': self.caseName,
                  'priority': self.priority,
                  'relatedDemand': self.relatedDemand,
                  'relatedTask': self.relatedTask,
                  'casePath': self.casePath
                  }
        self.commonParams.update(params)
        if self.caseName != self.lastCaseNo:
            self.lastCaseNo = self.caseName
            self.isNewCase = True
            self.stepOrder = 1
        else:
            self.isNewCase = False
            self.stepOrder += 1

    def __getCurrentCase(self):
        caseInfo = dict()
        caseInfo['caseNo'] = self.caseNo
        caseInfo['caseName'] = self.caseName
        caseInfo['priority'] = self.priority
        caseInfo['isPass'] = self.isPass
        caseInfo['preconditions'] = self.preconditions
        caseInfo['operator'] = self.operator
        caseInfo['menuFunction'] = self.menuFunction
        caseInfo['caseStep'] = self.caseStep
        caseInfo['inputData'] = self.inputData
        caseInfo['inputDataDict'] = self.inputDataDict
        caseInfo['expectResult'] = self.expectResult
        caseInfo['expectResultDict'] = self.expectResultDict
        caseInfo['uiExpectResult'] = self.uiExpectResult
        caseInfo['dataDesignDesc'] = self.dataDesignDesc
        caseInfo['relatedDemand'] = self.relatedDemand
        caseInfo['relatedTask'] = self.relatedTask
        caseInfo['casePath'] = self.casePath
        logger.info(f"caseInfo : {caseInfo}")
        return caseInfo

    def getPriority(self):
        return self.priorityList.index(self.priority)

    def getIsPass(self):
        return 0 if self.isPass == "正" else 1

    def update_params(self, **kwargs):
        self.caseNo = kwargs.get("流水号") if kwargs.get("流水号") else self.commonParams.get('caseNo')
        self.caseName = kwargs.get("测试点") if kwargs.get("测试点") else self.commonParams.get('caseName')
        self.priority = kwargs.get("优先级") if kwargs.get("优先级") else self.commonParams.get('priority', 'P2')
        self.isPass = kwargs.get("正反例") if kwargs.get("正反例") else self.commonParams.get('isPass', '正')
        self.relatedDemand = kwargs.get("关联需求单") if kwargs.get("关联需求单") else self.commonParams.get('relatedDemand')
        self.relatedTask = kwargs.get("关联任务单") if kwargs.get("关联任务单") else self.commonParams.get('relatedTask')
        self.casePath = kwargs.get("用例路径") if kwargs.get("用例路径") else self.commonParams.get('casePath')
        self.__updateCommonParams()
        self.preconditions = kwargs.get("前置条件") if kwargs.get("前置条件") else ''
        self.operator = kwargs.get("用户（操作员）") if kwargs.get("用户（操作员）") else ''
        self.menuFunction = kwargs.get("菜单功能") if kwargs.get("菜单功能") else ''
        self.caseStep = kwargs.get("操作步骤") if kwargs.get("操作步骤") else ''
        self.inputData = kwargs.get("输入数据") if kwargs.get("输入数据") else ''
        self.expectResult = kwargs.get("预期结果") if kwargs.get("预期结果") else ''
        self.dataDesignDesc = kwargs.get("数据设计说明") if kwargs.get("数据设计说明") else ''
        self.uiExpectResult = kwargs.get("界面期望检出") if kwargs.get("界面期望检出") else ''
        self.__dealInputData()
        self.__getCurrentCase()

    def __dealInputData(self):
        rows = self.inputData.split("\n")
        self.inputDataDict = {}
        for unit in rows:
            line = re.sub(r'：', ":", unit, 1)
            if line:
                lines = line.split(":")
                lines = [unit.strip() for unit in lines]
                self.inputDataDict[lines[0]] = "".join(lines[1:])
        if self.casePath.startswith('合规5.0'):   # 合规5.0 记录脚本路径，用以拼接脚本 2025-04-14
            self.inputDataDict['脚本路径'] = '\\'.join(self.casePath.split('/')[2:])
        # 输入时 保留原样
        self.inputData = "\r\n".join(rows)
        return self.inputDataDict

    def dealPreconditions(self, params: dict):
        """
        从前置条件提取关键入参
        """
        preconditions = params.get("preconditions", "")
        for unit in preconditions:
            res = re.findall(unit, self.preconditions)
            if res and len(res) > 2:
                temp_dict = {dd[0].strip(): dd[1].strip() for dd in res}
                logger.info(f"dealPreconditions temp_dict: {temp_dict}")
                self.inputDataDict.update(temp_dict)
            # 20250522 新增分组提取模式 r'监控中心首页-(?P<场景名称>[^-]+-[^-]+)$'
            res2 = re.search(unit, self.preconditions)
            if res2.groupdict():
                temp_dict = res2.groupdict()
                logger.info(f"dealPreconditions temp_dict: {temp_dict}")
                self.inputDataDict.update(temp_dict)

    def dealExpectResult(self, params: dict):
        """
        提前期望结果
        """
        self.expectResultDict = {}
        expectPattern = params.get("expectPattern", [])
        expectKey = params.get("expectKey", [])
        for i, exp in enumerate(expectPattern):
            res = re.findall(exp, self.expectResult, re.I|re.M)
            if res:
                for unit in res:
                    if expectKey and len(expectKey) > i:
                        key_ext = unit[0].strip() + expectKey[i]
                    else:
                        key_ext = unit[0].strip()
                    if unit[1].strip():     # key-value 添加时，过滤value为空的情况 20250414
                        self.expectResultDict[key_ext] = unit[1].strip()
        logger.info(f'dealExpectResult : {self.expectResultDict}')
        expect_result = ""
        for key, value in self.expectResultDict.items():
            expect_result += f"{key}={value};"
        self.expectResultString = expect_result.rstrip(';')
        return self.expectResultDict


class BladeCase:
    def __init__(self, CaseStatusObject, systemParams: dict=None):
        self.filepath = None
        self.CaseStatus = CaseStatusObject
        self.caseModuleName = None
        self.systemParams = systemParams
        self.projectRootPath = systemParams.get("projectRootPath", "")
        self.ECD = ExcelCaseDict()
        self.CASE_LIST = None
        self.CASE_PARAMS_DF = None
        self.scriptConfig = YamlOperation(systemParams.get("configPath", "")).readYaml()
        self.sqlInsertCases = self.scriptConfig.get("SqlInsertCases", [])
        self.projectInfo = self.scriptConfig.get("project", {})
        self.httpClient = HttpRequest()
        self.headers = {
            'Projectid': '289aa8a63edf4ca4a664743d6e5e9c5c',
            'Connection': 'keep-alive',
            'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
            'Cookie': ''
        }

    @staticmethod
    def get_uuid_32():
        temp = uuid.uuid4()
        return temp.hex

    # 判断脚本类型
    def __judge_case_type(self, flag=False):
        """
        判断脚本类型。优先通过输入参数的关键字进行匹配，匹配不到则从步骤描述中进行匹配
        :return: 脚本对应的类型名称
        """
        script_patterns = self.scriptConfig.get('ScriptPattern')
        script_type = 'common_none'
        content = script_patterns[0]
        # logger.info(f"init content: {content}")
        input_string = ",".join(list(self.ECD.inputDataDict.keys()))
        for unit in script_patterns:
            input_patterns = unit.get("inputPattern", [])
            input_flag = False
            if input_patterns:
                for pattern in input_patterns:
                    res = re.search(pattern, input_string, re.I)
                    if res:
                        input_flag = True
                        break
            if input_flag:
                fund = True
            else:
                step_patterns = unit.get("stepPattern", [])
                step_flag = False
                step_desc = self.ECD.caseStep
                if step_patterns:
                    for pattern in step_patterns:
                        res = re.search(pattern, step_desc, re.I)
                        if res:
                            step_flag = True
                            break
                if step_flag:
                    fund = True
                else:
                    fund = False
            if fund:
                script_type = unit.get("scriptType")
                content = unit
                break
        logger.info(f"__judge_case_type： {script_type}, content: {content}")
        if flag:
            return content
        else:
            return script_type

    # 查找期望断言
    def __fund_expect_pattern(self, scriptType):
        scripts = self.scriptConfig.get('ScriptPattern')
        results = jsonpath.jsonpath(scripts, f"$.[?(@.scriptType=='{scriptType}')]")
        if results:
            return results[0]
        else:
            return None

    # 获取测试数据
    def __get_test_data(self, script_name):
        input_dict = self.ECD.inputDataDict
        input_dict_new = dict()
        for key, value in input_dict.items():
            if value.startswith("$D") and self.CASE_PARAMS_DF is not None:
                # 参数值是变量引用，此处替换成真实值。 2024-12-11修改
                value = OperationExcel.filter_row_by_col('数据编号', value, key, self.CASE_PARAMS_DF)
                input_dict_new[key] = value[0]
            else:
                input_dict_new[key] = value
        keys = list(input_dict.keys())
        expect_string = self.ECD.expectResultString
        if script_name == '空脚本空步骤':
            logger.info(f"无数据，无需绑定！")
            return []
        if not expect_string:
            expect_string = "resp_status_code=200"
        data_chose_row = self.get_uuid_32()
        first_row = ["", "序号", "期望"] + keys
        second_row = ["", "参数说明", ""] + ["" for _ in keys]
        third_row = [data_chose_row, "No.1", expect_string] + list(input_dict_new.values())
        if script_name in self.sqlInsertCases:
            first_row.append("CaseNo")
            second_row.append("查找预置sql文件")
            third_row.append(str(self.ECD.caseNo))
        second_row = ["", "参数说明", ""] + ["" for _ in keys]
        data_content = {
            "id": "",
            "dataChoseRow": data_chose_row,
            "content": quote("测试数据"),
            "dataArrContent": [
                {"Sheet0": [first_row, second_row, third_row]}
            ]
        }
        return data_content

    # 查询脚本ID
    def __query_script_id(self, script_name):
        url = "https://blade.hundsun.com/openapi/design/getScriptIdByScriptName.json"
        params_temp = {"account": self.systemParams.get("username")}
        params_temp.update(self.projectInfo)
        params_temp["scriptNames"] = [script_name]
        response = self.httpClient.doGetMethod(url, data=json.dumps(params_temp),
                                               headers={"Content-Type": "application/json;charset=UTF-8"})
        res_data = response.json().get("data")
        script_data = res_data.get(script_name)
        logger.info(f"获取脚本 {script_name} 的id: {script_data}")
        return script_data[0]

    # 查询自定义步骤的字段名称
    def __query_case_step_name(self):
        url = "http://cloud.blade.hundsun.com/shareInterface/queryCaseStepKey.json"
        params_temp = {"account": self.systemParams.get("username")}
        params_temp.update(self.projectInfo)
        response = self.httpClient.doGetMethod(url, data=json.dumps(params_temp),
                                               headers={"Content-Type": "application/json;charset=UTF-8"})
        keys = response.json().get("result_list")
        logger.info(f"获取自定义步骤的字段名称: {keys}")
        result = dict()
        key_list = list()
        for unit in keys:
            key = unit["colName"]
            value = unit["colKey"]
            if key in key_list:
                old_value = result.get(key, None)
                if old_value != value and unit["colSrc"] == "defined":
                    result[key] = old_value + ',' + value
            else:
                key_list.append(key)
                result[key] = value
        logger.info(f"step name : {result}")
        caseFields = self.scriptConfig.get("CaseFields", None)
        if caseFields:
            result.update(caseFields)
            logger.info(f"new step name : {result}")
        return result

    # 上传用例
    def __upload_case(self, params_temp):
        url = "https://blade.hundsun.com/openapi/design/importOfflineCaseV2.json"
        logger.info(f"upload case params: {params_temp}")
        result = self.httpClient.doPost(url, json.dumps(params_temp),
                                        {"Content-Type": "application/json;charset=UTF-8"})
        if result.status_code == 200 and result.json().get('code') != "1000":
            logger.warning(f"case upload error : {result.json()}")
        elif result.status_code == 200 and result.json().get('code') == "1000":
            logger.info(f"upload case success: {params_temp}")
        else:
            logger.error(f"response error : {result.json()}")

    def set_case_list(self, case_list: list):
        self.CASE_PARAMS_DF = None
        self.CASE_LIST = case_list

    def uploadTestCaseApi(self, readFlag=True):
        self.CaseStatus.set_progress(self.systemParams.get('username'), 0)
        if readFlag:
            filepath, filename = os.path.split(self.projectRootPath)
            OEX = OperationExcel(filename, "用例", filepath)
            OEX.getSheets()
            self.CASE_LIST = OEX.readExcelDatas("用例")
            if "数据" in OEX.sheet_names:
                OEX.read_sheet_by_name("数据")
                self.CASE_PARAMS_DF = OEX.sheet_data.get("数据")
        logger.info(f"datas: {self.CASE_LIST}")
        cols_name = self.__query_case_step_name()
        params_temp = {"account": self.systemParams.get("username"), "nodePath": "", "data": []}
        params_temp.update(self.projectInfo)
        case_one = None
        step_index = 1
        count = 0
        total = len(self.CASE_LIST)
        total = total if total > 0 else 1
        for index, line in enumerate(self.CASE_LIST):
            self.ECD.update_params(**line)     # 更新用例步骤信息
            count = index + 1
            if self.ECD.isNewCase:
                if index > 0:   # 上传上一条用例
                    params_temp['data'].append(case_one)
                    self.__upload_case(params_temp)
                    params_temp['data'].clear()
                    progress = round(100 * count / total, 2)
                    self.CaseStatus.set_progress(self.systemParams.get('username'), progress)
                case_path_list = self.ECD.casePath.split('/')
                params_temp['nodePath'] = quote('/'.join(case_path_list[1:]))
                step_index = 1
                # 每一条用例的用例信息
                case_one = {
                    "caseName": quote(self.ECD.caseName),
                    # "caseNo": self.ECD.caseNo.lstrip("C"),
                    "caseType": self.systemParams.get("caseType"),
                    "caseState": "1",
                    "casePrior": self.ECD.priority,
                    "caseSideType": str(self.ECD.getIsPass()),
                    "caseLayer": "1",
                    "caseDetailType": "0",
                    "caseHeader": self.systemParams.get("username"),
                    "caseKeyword": quote(self.ECD.menuFunction),
                    "step": []
                }
                if self.ECD.caseNo:
                    case_no_cur = self.ECD.caseNo.lstrip("C")
                    case_one["caseNo"] = case_no_cur[0] if isinstance(case_no_cur, tuple) or isinstance(case_no_cur, list) else case_no_cur
            # 每一步的步骤信息
            per_condition_keys = cols_name.get("前置条件").split(',')
            description_keys = cols_name.get("备注").split(',')
            step_json = {
                "stepDes": quote(self.ECD.caseStep),
                "Expect": quote(self.ECD.expectResult),
                "inputData": quote(self.ECD.inputData),
                "precisionTest": "1" if self.ECD.dataDesignDesc == "后置" or self.ECD.dataDesignDesc == "后置步骤" else "2"
            }
            for key in per_condition_keys:
                step_json[key] = quote(self.ECD.preconditions)
            for key in description_keys:
                step_json[key] = quote(self.ECD.dataDesignDesc)
            if self.systemParams.get("caseType") == '2':       # 若是自动化用例，绑定脚本和测试数据
                script_content = self.__judge_case_type(flag=True)
                self.ECD.dealPreconditions(script_content)
                self.ECD.dealExpectResult(script_content)
                script_data = self.__query_script_id(script_content.get('scriptName'))
                step_json["scriptContent"] = script_data.get("scriptId")
                data_content = self.__get_test_data(script_content.get('scriptName'))
                if data_content:
                    step_json["dataContent"] = data_content
            step = {
                "stepName": quote(self.ECD.caseName + "-" + str(step_index)),
                "stepJson": step_json
            }
            logger.info("step json : {}".format(step))
            step_index += 1
            case_one["step"].append(step)           # 步骤添加到用例中
        else:
            params_temp['data'].append(case_one)
            self.__upload_case(params_temp)
            progress = round(100 * count / total, 2)
            logger.info(f"progress: {progress}, total: {total}, count: {count}")
            self.CaseStatus.set_progress(self.systemParams.get('username'), progress)
        return progress